package unittests;

import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import spreadsheet.*;

public class FormulaParserTest {
    
	private SpreadSheet spreadSheet = new SpreadSheet();
	private FormulaParser parser = FormulaParser.getInstance();
	private String result;
        private double dFuzz = 0.0000001;

	@Before
	public void setUp() {
		spreadSheet = new SpreadSheet();
		parser = FormulaParser.getInstance();
		parser.setSpreadSheet(spreadSheet);
	}

	@After
	public void tearDown() {
		spreadSheet = null;
		parser = null;
	}

	@Test
	public void testParseLiterals() {
		try {
			result = parser.parse("=1.0");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-1");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-1.5");
			assertEquals( -1.5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("parse me if you can");
			assertEquals( result, "parse me if you can" );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse("");
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse(null);
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse("=1.5.9");
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=Equal or not equal. This is the question." );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=.01" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=." );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1." );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
        }

	@Test
	public void testParseMultiply() {
		try {
			result = parser.parse("=1.0*1.0");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=2.0*2.0");
			assertEquals( 4, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=3.0*7.0");
			assertEquals( 21, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=11.0*3.0");
			assertEquals( 33, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1.0*0.0");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-1.0*1.0");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0.0*0.0");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1*1");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1.0*1");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1*1.0");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1*1.0*1.0");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0.1*1.0*1");
			assertEquals( 0.1, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=null*nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=*" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=**" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1**1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=*1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1*" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "==1*1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1*nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}

	@Test
	public void testParseDivide() {
		try {
			result = parser.parse("=1/1");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=2/2");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=3/6");
			assertEquals( 0.5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=12/3");
			assertEquals( 4, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-1/1");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=5/-4");
			assertEquals( -1.25, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0/8");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0/-7");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=7/-7.0");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1.0/-7");
			assertEquals( -1/7, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=7/-7/1");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0.7/-7.0/1");
			assertEquals( -0.1, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=null/nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=/" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=//" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1//1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=/1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1/" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "==1/1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1/nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}

	@Test(expected = Exception.class)
	public void testParseDivideByZero() throws Exception {
		result = parser.parse("=2/0");
	}

	@Test
	public void testParseAdd() {
		try {
			result = parser.parse("=2+3");
			assertEquals( 5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-1+3");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-2+-3");
			assertEquals( -5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=4+-4");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1.66+0.5");
			assertEquals( 2.16, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=2+-4");
			assertEquals( -2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0.001+0.999");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1.0+0.999");
			assertEquals( 1.999, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1+1+1");
			assertEquals( 3, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=0.1+1.0+1");
			assertEquals( 2.1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=+1");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=1++1");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=null+nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=+" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=++" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1+" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "==1+1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1+nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}

	@Test
	public void testParseSubstaction() {
		try {
			result = parser.parse("=42-5");
			assertEquals( 37, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=5-10");
			assertEquals( -5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-2--3");
			assertEquals( 1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=42-42");
			assertEquals( 0, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=4-2");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=2.5-0.75");
			assertEquals( 1.75, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=5-5.5");
			assertEquals( -0.5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-42--41");
			assertEquals( -1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=4-1-1");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=4.0-1-1.0");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                
                try {
                        result = parser.parse( "=null-nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=-" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=--" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1-" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "==1-1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=1-nada" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}

	@Test
	public void testParseEquation() {
		try {
			result = parser.parse("=42-5+1*2");
			assertEquals( 39, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=5*10-20/2");
			assertEquals( 40, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=1=1" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=*./+-" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}

	@Test
	public void testParseParentheses() {
		try {
			result = parser.parse("=42-(5+1)*2");
			assertEquals( 30, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=5*(10-20)/2");
			assertEquals( -25, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(20-10)/2");
			assertEquals( 5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(2)");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(2.0)");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(-2.0)");
			assertEquals( -2, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(+2.0)");
			assertEquals( 2, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=(" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=)" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=()" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=)(" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=(=)" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=(.)" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}
	
	@Test
	public void testParseCellNames() {
		spreadSheet.getCells()[0][0].setCellValue("10");
		spreadSheet.getCells()[0][0].setCellType(SpreadSheetCell.CELL_TYPES_PRIMITIVE);
		spreadSheet.getCells()[1][0].setCellValue("5");
		spreadSheet.getCells()[1][0].setCellType(SpreadSheetCell.CELL_TYPES_PRIMITIVE);
                spreadSheet.getCells()[2][0].setCellValue("6.1");
		spreadSheet.getCells()[2][0].setCellType(SpreadSheetCell.CELL_TYPES_PRIMITIVE);
		try {
			result = parser.parse("=A1+1");
			assertEquals( 11, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1+1");
			assertEquals( 11, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1*2");
			assertEquals( 20, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1/2");
			assertEquals( 5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1-1");
			assertEquals( 9, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1-A2");
			assertEquals( 5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1");
			assertEquals( 10, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1-A2+A3");
			assertEquals( 11.1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=-A1");
			assertEquals( 11.1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=+A1");
			assertEquals( 11.1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1-(A2)");
			assertEquals( 5, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=(A1-A2)+A3");
			assertEquals( 11.1, Double.parseDouble( result ), dFuzz );
                        
                        result = parser.parse("=A1-(A2+A3)");
			assertEquals( -1.1, Double.parseDouble( result ), dFuzz );
		} catch (Exception e) {
                        e.printStackTrace();
			fail(e.getMessage());
		}
                try {
                        result = parser.parse( "=A1+" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=A" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=A1+()" );
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
                try {
                        result = parser.parse( "=AAA1" );//this column does not exist
                        fail( "There should have been an exception thrown here" );
                } catch (Exception e) {}
	}
	
	@Test(expected=Exception.class)
	public void testParseCellNamesLoop() throws Exception {
		spreadSheet.getCells()[0][0].setCellValue("=A2+1");
		spreadSheet.getCells()[0][0].setCellType(SpreadSheetCell.CELL_TYPES_FORMULA);
		spreadSheet.getCells()[1][0].setCellValue("=A1+1");
		spreadSheet.getCells()[1][0].setCellType(SpreadSheetCell.CELL_TYPES_FORMULA);
		
		result = parser.parse("=A1-A2");
	}
	
	/**
	 * Test method for {@link spreadsheet.FormulaParser#getInstance()}.
	 */
	@Test
	public final void testGetInstance() {
		assertNotNull("getInstanceResult",FormulaParser.getInstance());
	}
}